<?php

/**
 * Image - Provides an Interface to the GD-Library for image manipulation
 *
 *
 * @license MIT-style License
 * @author Christoph Pojer <christoph.pojer@gmail.com>
 * @author Additions: Fabian Vogelsteller <fabian.vogelsteller@gmail.com>
 * @author Additions: Ger Hobbelt <ger@hobbelt.com>
 *
 * @link http://www.bin-co.com/php/scripts/classes/gd_image/ Based on work by "Binny V A"
 *
 * @version 1.12
 * Changelog
 *    - 1.12 added memory usage guestimator to warn you when attempting to process overlarge images which will silently but fataly crash PHP
 *    - 1.11 fixed $ratio in resize when both values are given
 *    - 1.1 add real resizing, with comparison of ratio
 *    - 1.01 small fixes in process method and add set memory limit to a higher value
 */
define('IMAGE_PROCESSING_MEMORY_MAX_USAGE', 64); // memory_limit setting, in Megabytes; increase when Image class reports too often the images don't fit in memory.

class Image
{

    /**
     * The path to the image file
     *
     * @var string
     */
    private $file;
    /**
     * The image resource
     *
     * @var resource
     */
    private $image;
    /**
     * Metadata regarding the image
     *
     * @var array
     */
    private $meta;
    /**
     * Flags whether the image has been manipulated by this instance in any way and has not yet been saved to disk.
     */
    private $dirty;

    /**
     * @param string $file The path to the image file
     */
    public function __construct($file)
    {
        $this->dirty = false;

        $this->meta = self::checkFileForProcessing($file);

        // only set the new memory limit of IMAGE_PROCESSING_MEMORY_MAX_USAGE MB when the configured one is smaller:
        if ($this->meta['fileinfo']['memory_limit'] < IMAGE_PROCESSING_MEMORY_MAX_USAGE * 1024 * 1024) {
            ini_set('memory_limit', IMAGE_PROCESSING_MEMORY_MAX_USAGE . 'M'); //  handle large images
        }

        $this->file = $file;

        if ($this->meta['ext'] != 'jpeg') {
            $this->image = $this->create();

            $fn = 'imagecreatefrom' . $this->meta['ext'];
            $original = false;
            if (function_exists($fn)) {
                $original = @$fn($file);
            }
            if (!$original)
                throw new Exception('imagecreate_failed:' . $fn);

            if (!@imagecopyresampled($this->image, $original, 0, 0, 0, 0, $this->meta['width'], $this->meta['height'], $this->meta['width'], $this->meta['height']))
                throw new Exception('cvt2truecolor_failed:' . $this->meta['width'] . ' x ' . $this->meta['height']);
            imagedestroy($original);
            unset($original);
        }
        else {
            $this->image = @imagecreatefromjpeg($file);
            if (!$this->image)
                throw new Exception('imagecreate_failed:imagecreatefromjpeg');
        }
    }

    public function __destruct()
    {
        if (!empty($this->image))
            imagedestroy($this->image);
        unset($this->image);
    }

    /**
     * Return an array of supported extensions (rather: the second parts of the mime types!)
     *
     * A type is listed as 'supported' when it can be READ.
     */
    public static function getSupportedTypes()
    {
        static $supported_types;

        if (empty($supported_types)) {
            $gdi = gd_info();

            $supported_types = array();
            if (!empty($gdi['GIF Read Support']))
                $supported_types[] = 'gif';
            if (!empty($gdi['PNG Support']))
                $supported_types[] = 'png';
            if (!empty($gdi['JPEG Support']) || !empty($gdi['JPG Support']) /* pre 5.3.0 */)
                $supported_types[] = 'jpeg';
            if (!empty($gdi['WBMP Support']))
                $supported_types[] = 'wbmp';
            if (!empty($gdi['XPM Support']))
                $supported_types[] = 'xpm';
            if (!empty($gdi['XBM Support']))
                $supported_types[] = 'xbm';
            $supported_types[] = 'bmp';
        }
        return $supported_types;
    }

    /**
     * Guestimates how much RAM memory must be available to be able to process the given image file.
     *
     * @return an array with key,value pairs: 'needed' specifies the guestimated minimum amount of free
     *         memory required for the given file, 'memory_limit' is an integer value representing the total
     *         amount of bytes reserved for PHP script, while 'will_fit' is a boolean which indicates whether
     *         the given image file could potentially be loaded and processed without causing fatal out-of-memory
     *         errors.
     *         The directory separator and path-corrected filespec is returned in the 'path' value.
     *
     * @note The given image file must exist on disk; if it does not, 'needed' and 'will_fit' keys will not
     *       be present in the returned array.
     */
    public static function guestimateRequiredMemorySpace($file)
    {
        $val = trim(ini_get('memory_limit'));
        $last = strtoupper(substr($val, -1, 1));
        $val = floatval($val); // discards the KMG postfix, allow $val to carry values beyond 2..4G
        switch ($last) {
            // The 'G' modifier is available since PHP 5.1.0
            case 'G':
                $val *= 1024.0;
            case 'M':
                $val *= 1024.0;
            case 'K':
                $val *= 1024.0;
                break;
        }
        $limit = $val;

        $in_use = (function_exists('memory_get_usage') ? memory_get_usage() : 1000000 /* take a wild guess, er, excuse me, 'apply a heuristic' */ );

        $rv = array(
            'memory_limit' => $limit,
            'mem_in_use' => $in_use,
            'path' => $file
        );

        if (file_exists($file)) {
            $raw_size = @filesize($file);
            $rv['filesize'] = $raw_size;

            $img = @getimagesize($file, $info_ex);
            if ($img) {
                $width = $img[0];
                $height = $img[1];
                $rv['imageinfo'] = $img;
                $rv['imageinfo_extra'] = $info_ex;

                // assume RGBA8, i.e. 4 bytes per pixel
                // ... having had a good look with memory_get_usage() and memory_get_peak_usage(), it turns out we need
                // a 'fudge factor' a.k.a. heuristic as the '4 bytes per pixel' estimate is off by quite a bit (if we have
                // to believe the numbers following a single GD image load operation):
                $needed = 4.0 * $width * $height;
                $needed *= 34.0 / 27.0;
                $rv['needed'] = $needed;

                // since many operations require a source and destination buffer, that'll be 2 of them buffers, thank you very much:
                // ... however, having had a good look with memory_get_usage() and memory_get_peak_usage(), it turns out
                // we need about triple!
                $will_eat = $needed * 2.8;
                // ^^^ factor = 2.8 : for very large images the estimation error is now ~ +1..8% too pessimistic. Soit!
                //     (tested with PNG images which consumed up to 475MB RAM to have their thumbnail created. This took a few
                //      seconds per image, so you might ask yourself if being able to serve such memory megalodons would be,
                //      ah, desirable, when considered from this perspective.)
                // and 'worst case' (ahem) we've got the file itself loaded in memory as well (on initial load and later save):
                // ... but this is more than covered by the 'triple charge' already, so we ditch this one from the heuristics.
                if (0)
                    $will_eat += $raw_size;

                // interestingly, JPEG files only appear to require about half that space required by PNG resize processes...
                if (!empty($img['mime']) && $img['mime'] == 'image/jpeg') {
                    $will_eat /= 2.0;
                }

                $rv['usage_guestimate'] = $will_eat;

                // now we know what we about need for this bugger, see if we got enough:
                $does_fit = ($limit - $in_use > $will_eat);
                $rv['usage_min_advised'] = $will_eat + $in_use;
                $rv['will_fit'] = $does_fit;
            } else {
                // else: this is not a valid image file!
                $rv['not_an_image_file'] = true;
            }
        } else {
            // else: this file does not exist!
            $rv['not_an_image_file'] = true;
        }
        return $rv;
    }

    /**
     * Check whether the given file is really an image file and whether it can be processed by our Image class, given the PHP
     * memory restrictions.
     *
     * Return the meta data array when the expectation is that the given file can be processed; throw an exception otherwise.
     */
    public static function checkFileForProcessing($file)
    {
        $finfo = self::guestimateRequiredMemorySpace($file);
        if (!empty($finfo['not_an_image_file']))
            throw new Exception('no_imageinfo');

        // is it a valid file existing on disk?
        if (!isset($finfo['imageinfo']))
            throw new Exception('no_imageinfo');

        $file = $finfo['path'];

        // only set the new memory limit of IMAGE_PROCESSING_MEMORY_MAX_USAGE MB when the configured one is smaller:
        if ($finfo['memory_limit'] < IMAGE_PROCESSING_MEMORY_MAX_USAGE * 1024 * 1024) {
            // recalc the 'will_fit' indicator now:
            $finfo['will_fit'] = ($finfo['usage_min_advised'] < IMAGE_PROCESSING_MEMORY_MAX_USAGE * 1024 * 1024);
        }

        $img = $finfo['imageinfo'];

        // and will it fit in available memory if we go and load the bugger?
        if (!$finfo['will_fit'])
            throw new Exception('img_will_not_fit:' . ceil($finfo['usage_min_advised'] / 1E6) . ' MByte');

        $explarr = explode('/', $img['mime']); // make sure the end() call doesn't throw an error next in E_STRICT mode:
        $ext_from_mime = end($explarr);
        $meta = array(
            'width' => $img[0],
            'height' => $img[1],
            'mime' => $img['mime'],
            'ext' => $ext_from_mime,
            'fileinfo' => $finfo
        );

        if ($meta['ext'] == 'jpg')
            $meta['ext'] = 'jpeg';
        else if ($meta['ext'] == 'bmp')
            $meta['ext'] = 'bmp';
        else if ($meta['ext'] == 'x-ms-bmp')
            $meta['ext'] = 'bmp';
        if (!in_array($meta['ext'], self::getSupportedTypes()))
            throw new Exception('unsupported_imgfmt:' . $meta['ext']);

        return $meta;
    }

    /**
     * Calculate the resize dimensions of an image, given the original dimensions and size limits
     *
     * @param int $orig_x the original's width
     * @param int $orig_y the original's height
     * @param int $x the maximum width after resizing has been done
     * @param int $y the maximum height after resizing has been done
     * @param bool $ratio set to FALSE if the image ratio is solely to be determined
     *                    by the $x and $y parameter values; when TRUE (the default)
     *                    the resize operation will keep the image aspect ratio intact
     * @param bool $resizeWhenSmaller if FALSE the images will not be resized when
     *                    already smaller, if TRUE the images will always be resized
     * @return array with 'width', 'height' and 'must_resize' component values on success; FALSE on error
     */
    public static function calculate_resize_dimensions($orig_x, $orig_y, $x = null, $y = null, $ratio = true, $resizeWhenSmaller = false)
    {
        if (empty($orig_x) || empty($orig_y) || (empty($x) && empty($y)))
            return false;

        $xStart = $x;
        $yStart = $y;
        $ratioX = $orig_x / $orig_y;
        $ratioY = $orig_y / $orig_x;
        $ratio |= ( empty($y) || empty($x)); // keep ratio when only width OR height is set
        //echo 'ALLOWED: <br>'.$xStart.'x'."<br>".$yStart.'y'."<br>---------------<br>";
        // ->> keep the RATIO
        if ($ratio) {
            //echo 'BEGINN: <br>'.$orig_x.'x'."<br>".$orig_y.'y'."<br><br>";
            // -> align to WIDTH
            if (!empty($x) && ($x < $orig_x || $resizeWhenSmaller))
                $y = $x / $ratioX;
            // -> align to HEIGHT
            elseif (!empty($y) && ($y < $orig_y || $resizeWhenSmaller))
                $x = $y / $ratioY;
            else {
                $y = $orig_y;
                $x = $orig_x;
            }
            //echo 'BET: <br>'.$x.'x'."<br>".$y.'y'."<br><br>";
            // ->> align to WIDTH AND HEIGHT
            if ((!empty($yStart) && $y > $yStart) || (!empty($xStart) && $x > $xStart)) {
                if ($y > $yStart) {
                    $y = $yStart;
                    $x = $y / $ratioY;
                } elseif ($x > $xStart) {
                    $x = $xStart;
                    $y = $x / $ratioX;
                }
            }
        }
        // else: ->> DONT keep the RATIO

        $x = round($x);
        $y = round($y);

        //echo 'END: <br>'.$x.'x'."<br>".$y.'y'."<br><br>";
        $rv = array(
            'width' => $x,
            'height' => $y,
            'must_resize' => false
        );
        // speedup? only do the resize operation when it must happen:
        if ($x != $orig_x || $y != $orig_y) {
            $rv['must_resize'] = true;
        }
        return $rv;
    }

    /**
     * Returns the size of the image
     *
     * @return array
     */
    public function getSize()
    {
        return array(
            'width' => $this->meta['width'],
            'height' => $this->meta['height'],
        );
    }

    /**
     * Returns a copy of the meta information of the image
     *
     * @return array
     */
    public function getMetaInfo()
    {
        return array_merge(array(), (is_array($this->meta) ? $this->meta : array()));
    }

    /**
     * Returns TRUE when the image data have been altered by this instance's operations, FALSE when the content has not (yet) been touched.
     *
     * @return boolean
     */
    public function isDirty()
    {
        return $this->dirty;
    }

    /**
     * Creates a new, empty image with the desired size
     *
     * @param int $x
     * @param int $y
     * @param string $ext
     * @return resource GD image handle on success; throws an exception on failure
     */
    private function create($x = null, $y = null, $ext = null)
    {
        if (!$x)
            $x = $this->meta['width'];
        if (!$y)
            $y = $this->meta['height'];

        $image = @imagecreatetruecolor($x, $y);
        if (!$image)
            throw new Exception('imagecreatetruecolor_failed');
        if (!$ext)
            $ext = $this->meta['ext'];
        if ($ext == 'png') {
            if (!@imagealphablending($image, false))
                throw new Exception('imagealphablending_failed');
            $alpha = @imagecolorallocatealpha($image, 0, 0, 0, 127);
            if (!$alpha)
                throw new Exception('imageallocalpha50pctgrey_failed');
            imagefilledrectangle($image, 0, 0, $x, $y, $alpha);
        }

        return $image;
    }

    /**
     * Replaces the image resource with the given parameter
     *
     * @param resource $new
     */
    private function set($new)
    {
        if (!empty($this->image))
            imagedestroy($this->image);
        $this->dirty = true;
        $this->image = $new;

        $this->meta['width'] = imagesx($this->image);
        $this->meta['height'] = imagesy($this->image);
    }

    /**
     * Returns the path to the image file
     *
     * @return string
     */
    public function getImagePath()
    {
        return $this->file;
    }

    /**
     * Returns the resource of the image file
     *
     * @return resource
     */
    public function getResource()
    {
        return $this->image;
    }

    /**
     * Rotates the image by the given angle
     *
     * @param int $angle
     * @param array $bgcolor An indexed array with red/green/blue/alpha values
     * @return resource Image resource on success; throws an exception on failure
     */
    public function rotate($angle, $bgcolor = null)
    {
        if (empty($this->image) || !$angle || $angle >= 360)
            return $this;

        $alpha = (is_array($bgcolor) ? @imagecolorallocatealpha($this->image, $bgcolor[0], $bgcolor[1], $bgcolor[2], !empty($bgcolor[3]) ? $bgcolor[3] : null) : $bgcolor);
        if (!$alpha)
            throw new Exception('imagecolorallocatealpha_failed');
        $img = @imagerotate($this->image, $angle, $alpha);
        if (!$img)
            throw new Exception('imagerotate_failed');
        $this->set($img);
        unset($img);

        return $this;
    }

    /**
     * Resizes the image to the given size, automatically calculates
     * the new ratio if parameter {@link $ratio} is set to true
     *
     * @param int $x the maximum width after resizing has been done
     * @param int $y the maximum height after resizing has been done
     * @param bool $ratio set to FALSE if the image ratio is solely to be determined
     *                    by the $x and $y parameter values; when TRUE (the default)
     *                    the resize operation will keep the image aspect ratio intact
     * @param bool $resizeWhenSmaller if FALSE the images will not be resized when
     *                    already smaller, if TRUE the images will always be resized
     * @return resource Image resource on success; throws an exception on failure
     */
    public function resize($x = null, $y = null, $ratio = true, $resizeWhenSmaller = false)
    {
        //CUONG INSERT
        if ($x > 1024) {
            $xTmp = $x;
            $x = 1024;
            $y = ($x * $y) / $xTmp;
        }
        
        if (empty($this->image) || (empty($x) && empty($y))) {
            throw new Exception('resize_inerr');
        }

        $dims = Image::calculate_resize_dimensions($this->meta['width'], $this->meta['height'], $x, $y, $ratio, $resizeWhenSmaller);
        if ($dims === false) {
            throw new Exception('resize_inerr:' . $this->meta['width'] . ' x ' . $this->meta['height']);
        }

        // speedup? only do the resize operation when it must happen:
        if ($dims['must_resize']) {
            $new = $this->create($dims['width'], $dims['height']);
            if (@imagecopyresampled($new, $this->image, 0, 0, 0, 0, $dims['width'], $dims['height'], $this->meta['width'], $this->meta['height'])) {
                $this->set($new);
                unset($new);
                return $this;
            }
            unset($new);
            throw new Exception('imagecopyresampled_failed:' . $this->meta['width'] . ' x ' . $this->meta['height']);
        } else {
            return $this;
        }
    }

    /**
     * Crops the image. The values are given like margin/padding values in css
     *
     * <b>Example</b>
     * <ul>
     * <li>crop(10) - Crops by 10px on all sides</li>
     * <li>crop(10, 5) - Crops by 10px on top and bottom and by 5px on left and right sides</li>
     * <li>crop(10, 5, 5) - Crops by 10px on top and by 5px on left, right and bottom sides</li>
     * <li>crop(10, 5, 3, 2) - Crops by 10px on top, 5px by right, 3px by bottom and 2px by left sides</li>
     * </ul>
     *
     * @param int $top
     * @param int $right
     * @param int $bottom
     * @param int $left
     * @return Image
     */
    public function crop($top, $right = null, $bottom = null, $left = null)
    {
        if (empty($this->image))
            return $this;

        if (!is_numeric($right) && !is_numeric($bottom) && !is_numeric($left))
            $right = $bottom = $left = $top;

        if (!is_numeric($bottom) && !is_numeric($left)) {
            $bottom = $top;
            $left = $right;
        }

        if (!is_numeric($left))
            $left = $right;

        $x = $this->meta['width'] - $left - $right;
        $y = $this->meta['height'] - $top - $bottom;

        if ($x < 0 || $y < 0)
            return $this;

        $new = $this->create($x, $y);
        if (!@imagecopy($new, $this->image, 0, 0, $left, $top, $x, $y))
            throw new Exception('imagecopy_failed');

        $this->set($new);
        unset($new);

        return $this;
    }

    /**
     * Flips the image horizontally or vertically. To Flip both copy multiple single pixel strips around instead
     * of just using ->rotate(180): no image distortion this way.
     *
     * @see Image::rotate()
     * @param string $type Either horizontal or vertical
     * @return Image
     */
    public function flip($type)
    {
        if (empty($this->image) || !in_array($type, array('horizontal', 'vertical')))
            return $this;

        $new = $this->create();

        if ($type == 'horizontal') {
            for ($x = 0; $x < $this->meta['width']; $x++) {
                if (!@imagecopy($new, $this->image, $this->meta['width'] - $x - 1, 0, $x, 0, 1, $this->meta['height']))
                    throw new Exception('imageflip_failed');
            }
        }
        elseif ($type == 'vertical') {
            for ($y = 0; $y < $this->meta['height']; $y++) {
                if (!@imagecopy($new, $this->image, 0, $this->meta['height'] - $y - 1, 0, $y, $this->meta['width'], 1))
                    throw new Exception('imageflip_failed');
            }
        }

        $this->set($new);
        unset($new);

        return $this;
    }

    /**
     * Stores the image in the desired directory or overwrite the old one
     *
     * @param string $ext
     * @param string $file
     * @param int $quality the amount of lossy compression to apply to the saved file
     * @param boolean $store_original_if_unaltered (default: FALSE) set to TRUE if you want to copy the
     *                                             original instead of saving the loaded copy when no
     *                                             edits to the image have occurred. (set to TRUE when
     *                                             you like to keep animated GIFs intact when they have
     *                                             not been cropped, rescaled, etc., for instance)
     *
     * @return Image object
     */
    public function process($ext = null, $file = null, $quality = 100, $store_original_if_unaltered = false)
    {
        if (empty($this->image))
            return $this;

        if (!$ext)
            $ext = $this->meta['ext'];
        $ext = strtolower($ext);

        if ($ext == 'jpg')
            $ext = 'jpeg';
        else if ($ext == 'png')
            imagesavealpha($this->image, true);

        if ($file == null)
            $file = $this->file;
        if (!$file)
            throw new Exception('process_nofile');
        if (!is_dir(dirname($file)))
            throw new Exception('process_nodir');
        if ($store_original_if_unaltered && !$this->isDirty() && $ext == $this->meta['ext']) {
            // copy original instead of saving the internal representation:
            $rv = true;
            if ($file != $this->file) {
                $rv = @copy($this->file, $file);
            }
        } else {
            $rv = false;
            $fn = 'image' . $ext;
            if (function_exists($fn)) {
                if ($ext == 'jpeg')
                    $rv = @$fn($this->image, $file, $quality);
                elseif ($ext == 'png')
                    $rv = @$fn($this->image, $file, 9); // PNG is lossless: always maximum compression!
 else
                    $rv = @$fn($this->image, $file);
            }
        }
        if (!$rv)
            throw new Exception($fn . '_failed');

        // If there is a new filename change the internal name too
        $this->file = $file;

        return $this;
    }

    /**
     * Saves the image to the given path
     *
     * @param string $file Leave empty to replace the original file
     * @param int $quality the amount of lossy compression to apply to the saved file
     * @param boolean $store_original_if_unaltered (default: FALSE) set to TRUE if you want to copy the
     *                                             original instead of saving the loaded copy when no
     *                                             edits to the image have occurred. (set to TRUE when
     *                                             you like to keep animated GIFs intact when they have
     *                                             not been cropped, rescaled, etc., for instance)
     *
     * @return Image
     */
    public function save($file = null, $quality = 100, $store_original_if_unaltered = false)
    {
        if (empty($this->image))
            return $this;

        if (!$file)
            $file = $this->file;

        $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
        if (!$ext) {
            $file .= '.' . $this->meta['ext'];
            $ext = $this->meta['ext'];
        }

        if ($ext == 'jpg')
            $ext = 'jpeg';

        return $this->process($ext, $file, $quality, $store_original_if_unaltered);
    }

    /**
     * Outputs the manipulated image. Implicitly overwrites the old one on disk.
     *
     * @return Image
     */
    public function show()
    {
        if (empty($this->image))
            return $this;

        header('Content-type: ' . $this->meta['mime']);
        return $this->process();
    }

}

if (!function_exists('imagecreatefrombmp')) {

    /**
     * http://nl3.php.net/manual/en/function.imagecreatefromwbmp.php#86214
     */
    function imagecreatefrombmp($filepath)
    {
        // Load the image into a string
        $filesize = @filesize($filepath);
        if ($filesize < 108 + 4)
            return false;

        $read = file_get_contents($filepath);
        if ($file === false)
            return false;

        $temp = unpack("H*", $read);
        unset($read);               // reduce memory consumption
        $hex = $temp[1];
        unset($temp);
        $header = substr($hex, 0, 108);

        // Process the header
        // Structure: http://www.fastgraph.com/help/bmp_header_format.html
        if (substr($header, 0, 4) == '424d') {
            // Cut it in parts of 2 bytes
            $header_parts = str_split($header, 2);

            // Get the width        4 bytes
            $width = hexdec($header_parts[19] . $header_parts[18]);

            // Get the height        4 bytes
            $height = hexdec($header_parts[23] . $header_parts[22]);

            // Unset the header params
            unset($header_parts);
        }

        // Define starting X and Y
        $x = 0;
        $y = 1;

        // Create newimage
        $image = imagecreatetruecolor($width, $height);
        if ($image === false)
            return $image;

        // Grab the body from the image
        $body = substr($hex, 108);
        unset($hex);

        // Calculate if padding at the end-line is needed
        // Divided by two to keep overview.
        // 1 byte = 2 HEX-chars
        $body_size = strlen($body) / 2;
        $header_size = $width * $height;

        // Use end-line padding? Only when needed
        $usePadding = ($body_size > $header_size * 3 + 4);

        // Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
        // Calculate the next DWORD-position in the body
        for ($i = 0; $i < $body_size; $i += 3) {
            // Calculate line-ending and padding
            if ($x >= $width) {
                // If padding needed, ignore image-padding
                // Shift i to the ending of the current 32-bit-block
                if ($usePadding)
                    $i += $width % 4;

                // Reset horizontal position
                $x = 0;

                // Raise the height-position (bottom-up)
                $y++;

                // Reached the image-height? Break the for-loop
                if ($y > $height)
                    break;
            }

            // Calculation of the RGB-pixel (defined as BGR in image-data)
            // Define $i_pos as absolute position in the body
            $i_pos = $i * 2;
            $r = hexdec($body[$i_pos + 4] . $body[$i_pos + 5]);
            $g = hexdec($body[$i_pos + 2] . $body[$i_pos + 3]);
            $b = hexdec($body[$i_pos] . $body[$i_pos + 1]);

            // Calculate and draw the pixel
            $color = imagecolorallocate($image, $r, $g, $b);
            if ($color === false) {
                imagedestroy($image);
                return false;
            }
            imagesetpixel($image, $x, $height - $y, $color);

            // Raise the horizontal position
            $x++;
        }

        // Unset the body / free the memory
        unset($body);

        // Return image-object
        return $image;
    }

}
